from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI
import os

app = FastAPI(title="NLU Processing Service")

# 配置参数（实际使用时应从环境变量或配置文件中读取）
OPENAI_API_BASE = os.getenv("OPENAI_API_BASE", "http://0.0.0.0:10085/v1")
MODEL_DOMAIN = os.getenv("MODEL_DOMAIN", "lora_domain")
MODEL_SEMANTIC = os.getenv("MODEL_SEMANTIC", "lora_semantic")

# 系统提示词配置
system_content_domain = """
### 角色定义 ###
你是一位文本语义领域分类专家，擅长根据给定的领域类别对用户输入的短文本进行分类。
### 核心技能 ###
精准解析用户指令的语义意图
准确匹配预定义一级领域类别
根据一级领域类别进一步准确匹配二级类别
### 任务说明 ###
根据用户自然语言指令和以下一级和二级类别列表，识别所属一级领域类别和二级领域类别，如果二级类别无法确认则只输出一级类别，一二级类别直接使用-连接
### 领域类别列表 ###
1. 空调
-空调开关
-温度调节
-制冷制热
-吹风模式
-风速调节
-出风口调节
-其他模式
2. 系统设置
-屏幕
-打开和关闭页面
-声音
-蓝牙和网络
-壁纸
3. 车辆控制
-车窗
-其他控制
-座椅通风
-车门控制
-座椅加热
-天窗
-遮阳帘
-座椅调节
-座椅按摩
4. 车辆信息查询
5. 导航
-导航
-搜索
-控制指令
6. 电话
-呼叫
-指令
7. 天气
-查询
8. 音乐
-播放歌曲
-歌曲控制
9. 视频
-视频播放
-视频控制
10. 应用
11. 日程
12. 新闻
13. 电台
14. 火车
15. 航班
16. 股票
17. 闲聊
### 格式示例 ###
- 输入： "打开车窗"   → 输出： "车辆控制-车窗"
- 输入： "明天气温多少" → 输出： "天气-查询"
- 输入： "你是谁" → 输出： "闲聊"

### 关键规则 ###
1. 确保理解用户意图并分别正确映射一二级类别
2. 仅输出类别文本（无其他说明或思考过程）
3. 请注意辨别闲聊的意图，如何语义属于聊天内容，则输出: "闲聊"
4. 只能输出给定的以上类别，不能创造新的类别
5. 未匹配类别时统一输出： "其他"
6. 若输入内容没有意义或涉及敏感，则输出： "其他"
7. 若没有明显的音乐搜索和视频搜索的意图，请输出"其他"或"闲聊"

### 错误示例 ###
输入： "打开空调"
错误输出： "应用"
正确输出： "空调-空调开关"

### 错误示例 ###
输入： "周杰伦"
错误输出： "音乐"
正确输出： "闲聊"

### 错误示例 ###
输入： "听周杰伦"
错误输出： "其他"
正确输出： "音乐"
"""


system_content_semantic = """
### 角色定义 ###
你是一位文本领域语义理解专家，擅长根据用户输入的短文本对给定的语义槽位进行填充。

### 核心技能 ###
精准解析用户指令的语义意图
准确匹配槽位，提取准确的槽值

### 任务说明 ###
理解文本所属领域和意图
理解每个槽位意义和可选项
从文本提取信息并填充槽值

### 格式示例 ###
-输入:
text:把空调风量调大三挡
domain:空调
intent:风速调节
mode:风速
degree:最高|中等|最低|高|低
object:空调
operate:调成|调低|调高
position:
value:

-输出：
text:把空调风量调大三挡
domain:空调
intent:风速调节
mode:风速
object:空调
operate:调高
value:3

### 格式示例 ###
-输入:
text:明天南京有雨吗
domain:天气
intent:查询
object:天气|晴|多云|阴|雨|雾霾|雪|气温
operate:查询
city:
time:

-输出:
text:明天南京有雨吗
domain:天气
intent:查询
object:雨
operate:查询
city:南京
time:明天

### 格式示例 ###
-输入:
text:导航到南京南站
domain:导航
intent:导航
origin:
destination:
strategy:默认|速度优先|费用优先|躲避拥堵|高速优先|不走高速|大路优先
waypoints:

-输出:
text:导航到南京南站
domain:导航
intent:导航
destination:南京南站
strategy:默认

### 格式示例 ###
-输入:
text:去南京南站顺便路过玄武湖
domain:导航
intent:导航
origin:
destination:
strategy:默认|速度优先|费用优先|躲避拥堵|高速优先|不走高速|大路优先
waypoints:

-输出:
text:去南京南站顺便路过玄武湖
domain:导航
intent:导航
destination:南京南站
strategy:默认
waypoints:玄武湖


### 格式示例 ###
-输入:
text:从南京南站去镇江南站最快的路
domain:导航
intent:路径规划
origin:
destination:
strategy:默认|速度优先|费用优先|躲避拥堵|高速优先|不走高速|大路优先
waypoints:

-输出:
text:从南京南站去镇江南站最快的路
domain:导航
intent:路径规划
destination:镇江南站
strategy:速度优先

### 关键规则 ###
1. 只能在给定的槽位范围内填充,不能增加新的槽位
2. 若槽位给出可选项,则只能在选项范围内选择,不能增加新的选项
3. 若槽位没有给出可选项,则需要根据文本进行提取
4. 提取内容要结合槽位的意义
5. 若无法提取有效的槽值,则不输出该槽位

"""

domain_en_dict = {'车辆控制': 'carControl',
 '空调': 'airControl',
 '系统设置': 'cmd',
 '导航': 'mapU',
 '音乐': 'musicX',
 '电话': 'telephone',
 '电台': 'radio',
 '视频': 'video',
 '天气': 'weather',
 '新闻': 'news',
 '火车': 'train',
 '航班': 'flight',
 '应用': 'app',
 '日程': 'scheduleX',
 '车辆信息查询': 'vehicleInfo',
 '股票': 'stock',
 '其他': 'other'}

# 语义模板示例（根据实际需求配置）
semantic_tmp = {'空调-空调开关': 'object:空调|出风口\noperate:打开|关闭|锁定|解锁\nposition:',
 '空调-制冷制热': 'object:空调\nmode:制冷|制热\ndegree:最高|中等|最低|极速|自动\noperate:打开|关闭\nposition:',
 '空调-其他模式': 'mode:同步|除雾|除霜|除湿|循环|节能|舒适|强劲|空气净化|负离子|空气监测\nobject:空调\noperate:打开|关闭\nposition:',
 '空调-风速调节': 'mode:风速\ndegree:最高|中等|最低|高|低\nobject:空调\noperate:调成|调低|调高\nposition:\nvalue:',
 '空调-温度调节': 'mode:温度\ndegree:最高|中等|最低|高|低\nobject:空调\noperate:调成|调高|调低\nposition:\nvalue:',
 '空调-吹风模式': 'mode:吹风\nobject:空调\noperate:打开|关闭|调成\nposition:',
 '空调-出风口调节': 'object:出风口\noperate:调成\nposition:\nplace:上|下|左|右|中',
 '系统设置-打开和关闭页面': 'mode:页面\nobject:\noperate:打开|关闭',
 '系统设置-蓝牙和网络': 'object:蓝牙|wifi|蜂窝移动数据|热点\noperate:打开|关闭|连接|断连',
 '系统设置-声音': 'object:\noperate:调高|调低|静音|查询|打开|关闭\nvalue:\nposition:',
 '系统设置-屏幕': 'mode:亮度\ndegree:最高|最低|中等\nobject:屏幕|仪表\noperate:调高|调低|调成\nvalue:\nposition:',
 '系统设置-壁纸': 'mode:壁纸\ntag:',
 '车辆控制-车窗': 'object:车窗|车窗锁\noperate:打开|关闭|暂停|调大|调小|调成\nposition:\nvalue:',
 '车辆控制-天窗': 'object:天窗\noperate:打开|关闭|暂停|调大|调小|调成\nposition:\nvalue:',
 '车辆控制-遮阳帘': 'object:遮阳帘\noperate:打开|关闭|暂停|调大|调小|调成\nposition:\nvalue:',
 '车辆控制-座椅加热': 'object:座椅|座椅靠背|座椅坐垫\nmode:加热\noperate:打开|关闭|暂停|调大|调小|调成\ndegree:最高|最低|高|中|低|自动\nposition:\nvalue:',
 '车辆控制-座椅通风': 'object:座椅|座椅靠背|座椅坐垫\nmode:通风\noperate:打开|关闭|暂停|调高|调低|调成\ndegree:最大|最小|高|中|低|自动\nposition:\nvalue:',
 '车辆控制-座椅按摩': 'object:座椅\nmode:按摩\nmodeValue:\noperate:打开|关闭|暂停|调高|调低|调成\ndegree:最高|最低|高|中|低|自动\nposition:\nvalue:',
 '车辆控制-座椅调节': 'object:座椅|座椅后背|座椅靠背\nmode:座椅调节\noperate:前进|后退|向上|向下|向左|向右|调成\ndegree:最前|最后\nposition:\nvalue:',
 '车辆控制-车门控制': 'object:车门\noperate:打开|关闭|暂停|调大|调小\nposition:',
 '车辆控制-其他控制': 'object:\nmode:\noperate:打开|关闭|暂停|调高|调低|调成|向左|向右\ndegree:最高|最低|手动\nvalue:\nposition:',
 '车辆信息查询':'object:\noperate:查询',
 '电话-呼叫':'name:\nnumber:\n',
 '电话-指令':'operate:',
 '应用':'object:\noperate:打开|关闭',
 '导航-导航':'origin:\ndestination:\nstrategy:默认|速度优先|费用优先|躲避拥堵|高速优先|不走高速|大路优先\nwaypoints:',
 '导航-搜索':'keywords:\nregion:',
 '导航-指令':'operate:',
 '天气-查询':'object:天气|晴|多云|阴|雨|雾霾|雪|气温\noperate:查询\ncity:\ntime:',
 '音乐-播放歌曲':'operate:播放\nsong:\nauthor:\nsource:\ntag:\nalbum:',
 '音乐-歌曲控制':'operate:切歌|播放|停止|向上切歌|向下切歌|重新播放\nmode:顺序|循环|随机|单曲循环',
 '视频-视频播放':'object:视频\noperate:打开|查询|播放\nauthor:\ntitle:\ntag:\nsource:',
 '视频-视频控制':'object:视频\noperate:暂停|播放|向下切换|收藏|取消收藏',
 }

# 初始化OpenAI客户端
client = OpenAI(
    api_key="EMPTY",  # vLLM不需要实际API密钥
    base_url=OPENAI_API_BASE
)

class NLURequest(BaseModel):
    query: str

class NLUResponse(BaseModel):
    result: dict


@app.post("/nlu", response_model=NLUResponse)
async def process_nlu(request: NLURequest):
    """处理自然语言理解请求，串联执行两个模型"""
    query = request.query
    result = {"text": query, "domain": "other", "intent": "", "slot": {}}
    return NLUResponse(result=result)
    # Step 1: 领域和意图识别
    domain_response = client.chat.completions.create(
        model=MODEL_DOMAIN,
        messages=[
            {"role": "system", "content": system_content_domain},
            {"role": "user", "content": query},
        ],
        temperature=0.3,
        max_tokens=16,
        extra_body={"chat_template_kwargs": {"enable_thinking": False}}
    )
    
    domain_intent = domain_response.choices[0].message.content
    domain, intent = domain_intent.split("-", 1) if "-" in domain_intent else (domain_intent, domain_intent)
    
    if domain in domain_en_dict:
        domain_en = domain_en_dict[domain]
    else:
        domain_en = "other"

    result = {"text": query, "domain": domain_en, "intent": intent, "slot": {}}

    # Step 2: 检查是否需要语义解析
    if domain_intent not in semantic_tmp:
        # result=f"domain:{domain}\nintent:{intent}\ntext:{query}"
        return NLUResponse(result=result)
        
    
    # Step 3: 语义解析处理
    semantic_content = f"domain:{domain}\nintent:{intent}\ntext:{query}\n{semantic_tmp[domain_intent]}"
    semantic_response = client.chat.completions.create(
        model=MODEL_SEMANTIC,
        messages=[
            {"role": "system", "content": system_content_semantic},
            {"role": "user", "content": semantic_content},
        ],
        temperature=0.3,
        max_tokens=128,
        extra_body={"chat_template_kwargs": {"enable_thinking": False}}
    )
    slot = {}
    for pair in semantic_response.choices[0].message.content.strip().split('\n'):  # 按行拆分
        key, value = pair.split(':', 1)        # 仅分割第一个冒号，避免值中含冒号
        slot[key.strip()] = value.strip() 
    result["slot"] = slot
    return NLUResponse(
        result=result
    )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=10086)